﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace MapObjectClassLibrary
{
    public partial class MapControl : UserControl
    {
        [ToolboxItem(true)]
        public MapControl()
        {
            InitializeComponent();
            this.MouseWheel += new MouseEventHandler(MapControl_MouseWheel);
        }

        #region 字段
        private Color _FillColor = Color.Tomato;
        private Color _BoundaryColor = Color.Black;
        private Color _TrackingColor = Color.DarkGreen;
        private bool _SelfMouseWheel = true;
        private double _DisplayScale = 1;
        private List<Polygon> _Polygons = new List<Polygon>();

        private Polygon mTrackingPolygon = new Polygon();


        enum MapOperationStyleEnum { non, zoomin, zoomout, pan, track }
        private MapOperationStyleEnum mMapOperationStyle = MapOperationStyleEnum.non;

        private const float mcBoundaryWidth = 1f;
        private PointF mCurrentMouseLocation=new PointF();
        private const float mcVectorHandleSize = 7f;
        private const float mcTrackingWidth = 2f;
        private const double mcZoomRatio = 1.2;

        private Cursor mCur_Cross = new Cursor(typeof(MapControl), "Resources.Cross.ico");
        private Cursor mCur_ZoomIn = new Cursor(typeof(MapControl), "Resources.ZoomIn.ico");
        private Cursor mCur_ZoomOut = new Cursor(typeof(MapControl), "Resources.ZoomOut.ico");
        private Cursor mCur_PanUp = new Cursor(typeof(MapControl), "Resources.PanUp.ico");

        /// <summary>
        /// 左上角点的坐标
        /// </summary>
        private double mOffsetX = 0, mOffsetY = 0;
        #endregion

        #region 属性
        /// <summary>
        /// 获取和设置填充颜色
        /// </summary>
        [Browsable(true), Description("获取和设置填充颜色")]
        public Color FillColor
        {
            get { return _FillColor; }
            set { _FillColor = value; }
        }

        /// <summary>
        /// 获取和设置边界颜色
        /// </summary>
        [Browsable(true), Description("获取和设置边界颜色")]
        public Color BoundaryColor
        {
            get { return _BoundaryColor; }
            set { _BoundaryColor = value; }
        }

        /// <summary>
        /// 获取和设置追踪颜色
        /// </summary>
        [Browsable(true), Description("获取和设置追踪颜色")]
        public Color TrackingColor
        {
            get { return _TrackingColor; }
            set { _TrackingColor = value; }
        }

        /// <summary>
        /// 获取和设置是否捕捉鼠标滚轮事件
        /// </summary>
        [Browsable(true), Description("获取和设置是否捕捉鼠标滚轮事件")]
        public bool SelfMouseWheel
        {
            get { return _SelfMouseWheel; }
            set { _SelfMouseWheel = value; }
        }

        /// <summary>
        /// 获取和设置比例尺
        /// </summary>
        [Browsable(false), Description("获取和设置比例尺")]
        public double DisplayScale
        {
            get { return _DisplayScale; }
        }

        [Browsable(false)]
        public Polygon[] Polygons
        {
            get { return _Polygons.ToArray(); }
            set
            {
                _Polygons.Clear();
                _Polygons.AddRange(value);
            }
        }
        #endregion

        #region 方法

        /// <summary>
        /// 地图坐标转换为屏幕坐标
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public PointD FromMapPoint(PointD point)
        {
            PointD zPoint = new PointD();
            zPoint.X = (point.X - mOffsetX) / _DisplayScale;
            zPoint.Y = (point.Y - mOffsetY) / _DisplayScale;
            return zPoint;
        }

        /// <summary>
        /// 屏幕坐标转换为地图坐标
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public PointD ToMapPoint(PointD point)
        {
            PointD zPoint = new PointD();
            zPoint.X = point.X * _DisplayScale + mOffsetX;
            zPoint.Y = point.Y * _DisplayScale + mOffsetY;
            return zPoint;
        }

        /// <summary>
        /// 指定中心和缩放系数，对地图进行缩放。不会实现自动重绘。
        /// </summary>
        /// <param name="center">中心</param>
        /// <param name="ratio">缩放系数</param>
        public void ZoomByCenter(PointD center, double ratio)
        {
            double zDispScale = _DisplayScale / ratio;
            double zOffsetX = mOffsetX + (1 - 1 / ratio) * (center.X - mOffsetX);
            double zOffsetY = mOffsetY + (1 - 1 / ratio) * (center.Y - mOffsetY);
            mOffsetX = zOffsetX;
            mOffsetY = zOffsetY;
            _DisplayScale = zDispScale;

        }

        /// <summary>
        /// 将地图操作方式设置为放大
        /// </summary>
        public void ZoomIn()
        {
            mMapOperationStyle = MapOperationStyleEnum.zoomin;
            this.Cursor = mCur_ZoomIn;
        }

        /// <summary>
        /// 将地图操作方式设置为缩小
        /// </summary>
        public void ZoomOut()
        {
            mMapOperationStyle = MapOperationStyleEnum.zoomout;
            this.Cursor = mCur_ZoomOut;
        }

        /// <summary>
        /// 将地图操作方式设置为漫游
        /// </summary>
        public void Pan()
        {
            mMapOperationStyle = MapOperationStyleEnum.pan;
            this.Cursor = mCur_PanUp;
        }

        /// <summary>
        /// 将地图操作方式设置为输入多边形
        /// </summary>
        public void TrackPolygon()
        {
            mMapOperationStyle = MapOperationStyleEnum.track;
            this.Cursor = mCur_Cross;
        }

        /// <summary>
        /// 向地图控件中添加多边形
        /// </summary>
        /// <param name="p"></param>
        public void AddPolygon(Polygon p)
        {
            _Polygons.Add(p);
        }
        #endregion

        #region 私有方法
        private void DrawPolygons(Graphics g)
        {
            SolidBrush zPolygonBrush = new SolidBrush(_FillColor);
            Pen zPolygonPen = new Pen(_BoundaryColor, mcBoundaryWidth);
            foreach (Polygon p in _Polygons)
            {
                PointF[] zps = new PointF[p.PointsCount];
                for (int i = 0; i < p.PointsCount; i++)
                {
                    PointD zp = FromMapPoint(p.Points[i]);
                    zps[i].X = (float)zp.X;
                    zps[i].Y = (float)zp.Y;
                }
                g.FillPolygon(zPolygonBrush, zps);
                g.DrawPolygon(zPolygonPen, zps);
            }
            zPolygonPen.Dispose();
            zPolygonBrush.Dispose();
        }
        private void DrawTrackingPolygon(Graphics g)
        {
            int zPointCount = mTrackingPolygon.PointsCount;
            if (0 == zPointCount) return;
            Pen zTrackingPen = new Pen(_TrackingColor, mcTrackingWidth);
            PointF[] zScreenPoints = new PointF[zPointCount];
            for (int i = 0; i < zPointCount; i++)
            {
                PointD zsp = FromMapPoint(mTrackingPolygon.Points[i]);
                zScreenPoints[i].X = (float)zsp.X;
                zScreenPoints[i].Y = (float)zsp.Y;
            }
            if (zPointCount > 1)
            {
                g.DrawLines(zTrackingPen, zScreenPoints);
            }
            SolidBrush zVertexBrush = new SolidBrush(_TrackingColor);
            for (int i = 0; i < zPointCount; i++)
            {
                RectangleF rf = new RectangleF(zScreenPoints[i].X - mcVectorHandleSize / 2, zScreenPoints[i].Y - mcVectorHandleSize / 2, mcVectorHandleSize, mcVectorHandleSize);
                g.FillRectangle(zVertexBrush, rf);
            }
            if (mMapOperationStyle == MapOperationStyleEnum.track)
            {
                if (1 == zPointCount)
                {
                    g.DrawLine(zTrackingPen, zScreenPoints[0], mCurrentMouseLocation);
                }
                else
                {
                    g.DrawLine(zTrackingPen, zScreenPoints[0], mCurrentMouseLocation);
                    g.DrawLine(zTrackingPen, zScreenPoints[zPointCount - 1], mCurrentMouseLocation);
                }
            }
            zTrackingPen.Dispose();
            zVertexBrush.Dispose();
        }
        #endregion

        #region 事件
        public delegate void TrackingFinishHandle(object sender,Polygon p);
        public event TrackingFinishHandle TrakingFinished;
        private void RaiseTrackingFinished(object sender,Polygon p)
        {
            if (null != TrakingFinished)
            {
                TrakingFinished(sender, p);
            }
        }
        private void RaiseTrackingFinished(Polygon p)
        {
            RaiseTrackingFinished(this, p);
        }

        public delegate void DisplayScaleChangeHandle(object sender);
        public event DisplayScaleChangeHandle DisplayScaleChanged;
        private void RaiseDisplayScaleChanged(object sender)
        {
            if (null != DisplayScaleChanged)
            {
                DisplayScaleChanged(sender);
            }
        }
        private void RaiseDisplayScaleChanged()
        {
            RaiseDisplayScaleChanged(this);
        }
        #endregion

        private void MapControl_Paint(object sender, PaintEventArgs e)
        {
            DrawPolygons(e.Graphics);
            DrawTrackingPolygon(e.Graphics);
        }

        private void MapControl_MouseDown(object sender, MouseEventArgs e)
        {
            switch (mMapOperationStyle)
            {
                case(MapOperationStyleEnum.non):break;
                case (MapOperationStyleEnum.pan):
                    if (e.Button == MouseButtons.Left && e.Clicks == 1)
                    {
                        mCurrentMouseLocation.X = e.Location.X;
                        mCurrentMouseLocation.Y = e.Location.Y;
                    }
                    break;
                case (MapOperationStyleEnum.track):
                    if (e.Button == MouseButtons.Left && e.Clicks == 1)
                    {
                        PointD zsp = new PointD(e.Location.X, e.Location.Y);
                        PointD zmp = ToMapPoint(zsp);
                        mTrackingPolygon.AddPoint(zmp);
                        Refresh();
                    }
                    break;
                case (MapOperationStyleEnum.zoomout):
                    ZoomByCenter(ToMapPoint(new PointD(e.Location.X, e.Location.Y)), 1/mcZoomRatio);
                    Refresh();
                    break;
                case (MapOperationStyleEnum.zoomin): 
                    ZoomByCenter(ToMapPoint(new PointD(e.Location.X, e.Location.Y)), mcZoomRatio);
                    Refresh();
                    break;
                default:
                    break;
            }
        }

        private void MapControl_MouseMove(object sender, MouseEventArgs e)
        {
            switch (mMapOperationStyle)
            {
                case (MapOperationStyleEnum.non): break;
                case (MapOperationStyleEnum.pan):
                    if (e.Button == MouseButtons.Left)
                    {
                        PointD pd = new PointD(mCurrentMouseLocation.X, mCurrentMouseLocation.Y);
                        pd = ToMapPoint(pd);
                        PointD pcd = new PointD(e.Location.X, e.Location.Y);
                        pcd = ToMapPoint(pcd);
                        mOffsetX -= pd.X - pcd.X;
                        mOffsetY -= pd.Y - pcd.Y;
                        Refresh();
                        mCurrentMouseLocation.X = e.Location.X;
                        mCurrentMouseLocation.Y = e.Location.Y;
                    }
                    break;
                case (MapOperationStyleEnum.track):
                    mCurrentMouseLocation.X = e.Location.X;
                    mCurrentMouseLocation.Y = e.Location.Y;
                    Refresh();
                    break;
                case (MapOperationStyleEnum.zoomin): break;
                case (MapOperationStyleEnum.zoomout): break;
                default:
                    break;
            }
        }

        private void MapControl_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            switch (mMapOperationStyle)
            {
                case (MapOperationStyleEnum.non): break;
                case (MapOperationStyleEnum.pan): break;
                case (MapOperationStyleEnum.track):
                    if (mTrackingPolygon.PointsCount > 2)
                    {
                        Polygon zp = mTrackingPolygon.Clone();
                        mTrackingPolygon.Clear();
                        //_Polygons.Add(zp);
                        RaiseTrackingFinished(this,zp);
                    }
                    Refresh();
                    break;
                case (MapOperationStyleEnum.zoomin): break;
                case (MapOperationStyleEnum.zoomout): break;
                default:
                    break;
            }
        }

        private void MapControl_MouseWheel(object sender, MouseEventArgs e)
        {
            if (_SelfMouseWheel)
            {
                if (e.Delta > 0)
                {
                    ZoomByCenter(ToMapPoint(new PointD(e.Location.X, e.Location.Y)), Math.Pow(mcZoomRatio * (-e.Delta), 1 / 10.0));
                }
                else
                {
                    ZoomByCenter(ToMapPoint(new PointD(e.Location.X, e.Location.Y)), Math.Pow(1 / mcZoomRatio / e.Delta, 1 / 10.0));
                }
                Refresh();
            }
        }
    }
}
